#include "Terrain.h"


Terrain::Terrain(void)
	:m_pDevice(NULL)
	,m_pVB(NULL)
	,m_pIB(NULL)
	,m_uiNumVertex(0)
	,m_uiNumFace(0)
{
	m_vecHeightmap.clear();
}


Terrain::~Terrain(void)
{
	//ReleaseCOM(m_pIB);
	//ReleaseCOM(m_pVB);
}

void Terrain::BuildVB()
{
	float x,y,z;
	std::vector<Vertex>		vertexs(m_uiNumVertex);

	float halfDepth = (m_terrainInfo.NumRows - 1)*m_terrainInfo.CellSpacing*0.5f;
	float halfWidth = (m_terrainInfo.NumCols - 1)*m_terrainInfo.CellSpacing*0.5f;

	float du = 1.0f / (m_terrainInfo.NumCols - 1);
	float dv = 1.0f / (m_terrainInfo.NumRows - 1);

	for(UINT i=0;i<m_terrainInfo.NumRows;++i)
	{
		z = halfDepth - i * m_terrainInfo.CellSpacing;
		for(UINT j=0;j<m_terrainInfo.NumCols;++j)
		{
			x = -halfWidth + j * m_terrainInfo.CellSpacing;

			y = m_vecHeightmap[i*m_terrainInfo.NumCols + j];

			vertexs[i*m_terrainInfo.NumCols + j].pos	= D3DXVECTOR3(x,y,z);
			vertexs[i*m_terrainInfo.NumCols + j].normal	= D3DXVECTOR3(0.0f,1.0f,0.0f);

			vertexs[i*m_terrainInfo.NumCols + j].texC.x	= j*du;
			vertexs[i*m_terrainInfo.NumCols + j].texC.y	= i*dv;
		}
	}

	float invTwoDx = 1.0f / (2.0f * m_terrainInfo.CellSpacing);
	float invTwoDz = 1.0f / (2.0f * m_terrainInfo.CellSpacing);

	float t,b,l,r;

	//compute the vertex's normal
	for(UINT i=2 ;i<m_terrainInfo.NumRows-1 ;++i)
	{
		for(UINT j=2 ;j<m_terrainInfo.NumCols-1 ;++j)
		{
			t = m_vecHeightmap[(i-1)*m_terrainInfo.NumCols + j];
			b = m_vecHeightmap[(i+1)*m_terrainInfo.NumCols + j];
			l = m_vecHeightmap[i*m_terrainInfo.NumCols + j - 1];
			r = m_vecHeightmap[i*m_terrainInfo.NumCols + j + 1];

			D3DXVECTOR3 tb(0.0f,(t-b)*invTwoDz,1.0f);
			D3DXVECTOR3 lr(1.0f,(l-r)*invTwoDx,0.0f);

			D3DXVECTOR3 N;
			D3DXVec3Cross(&N,&tb,&lr);
			D3DXVec3Normalize(&N,&N);

			vertexs[i*m_terrainInfo.NumCols+j].normal = N;
		}
	}

	D3D10_BUFFER_DESC vbd;
	vbd.Usage = D3D10_USAGE_IMMUTABLE;
	vbd.ByteWidth = sizeof(Vertex) * m_uiNumVertex;
	vbd.BindFlags = D3D10_BIND_VERTEX_BUFFER;
	vbd.CPUAccessFlags = 0;
	vbd.MiscFlags = 0;
	D3D10_SUBRESOURCE_DATA vinitData;
	vinitData.pSysMem = &vertexs[0];
	HR(m_pDevice->CreateBuffer(&vbd, &vinitData, &m_pVB));
}

void Terrain::BuildIB()
{
	std::vector<DWORD>	indices(m_uiNumFace*3);

	int curPos=0;

	for(UINT i=0 ;i<m_terrainInfo.NumRows-1 ;++i)
	{
		for(UINT j=0 ;j<m_terrainInfo.NumCols-1 ;++j)
		{
			indices[curPos]		= i*m_terrainInfo.NumCols+j;
			indices[curPos+1]	= i*m_terrainInfo.NumCols+j+1;
			indices[curPos+2]	= (i+1)*m_terrainInfo.NumCols+j;

			indices[curPos+3]	= (i+1)*m_terrainInfo.NumCols+j;
			indices[curPos+4]	= i*m_terrainInfo.NumCols+j+1;
			indices[curPos+5]	= (i+1)*m_terrainInfo.NumCols+j+1;

			curPos += 6;
		}
	}

	D3D10_BUFFER_DESC ibd;
	ibd.Usage = D3D10_USAGE_IMMUTABLE;
	ibd.ByteWidth = sizeof(DWORD) * m_uiNumFace*3;
	ibd.BindFlags = D3D10_BIND_INDEX_BUFFER;
	ibd.CPUAccessFlags = 0;
	ibd.MiscFlags = 0;
	D3D10_SUBRESOURCE_DATA iinitData;
	iinitData.pSysMem = &indices[0];
	HR(m_pDevice->CreateBuffer(&ibd, &iinitData, &m_pIB));
}

bool Terrain::IsBound( UINT x,UINT y )
{
	return x>=0 && x<m_terrainInfo.NumRows &&
			y>=0 && y<m_terrainInfo.NumCols;
}

float Terrain::Average( UINT x,UINT y )
{
	float num=0.0;
	float sum=0.0;

	for(UINT i=x-1 ;i<x+1 ;++i)
	{
		for(UINT j=y-1 ;j<y+1 ;++j)
		{
			if(IsBound(i,j))
			{
				sum += m_vecHeightmap[i*m_terrainInfo.NumCols+j];
				num += 1.0f;
			}
		}
	}

	return sum/num;
}

void Terrain::Smooth()
{
	std::vector<float> dest(m_vecHeightmap.size());

	for(UINT i=0 ;i<m_terrainInfo.NumRows ;++i)
	{
		for(UINT j=0 ;j<m_terrainInfo.NumCols ;++j)
		{
			dest[i*m_terrainInfo.NumCols+j] = Average(i,j);
		}
	}

	m_vecHeightmap = dest;
}

void Terrain::LoadHeightMap()
{
	// A height for each vertex
	std::vector<unsigned char> in( m_terrainInfo.NumRows * m_terrainInfo.NumCols );

	// Open the file.
	std::ifstream inFile;
	inFile.open(m_terrainInfo.HeightmapFilename.c_str(), std::ios_base::binary);

	if(inFile)
	{
		// Read the RAW bytes.
		inFile.read((char*)&in[0], (std::streamsize)in.size());

		// Done with file.
		inFile.close();
	}

	// Copy the array data into a float array, and scale
	// and offset the heights.
	m_vecHeightmap.resize(m_terrainInfo.NumRows * m_terrainInfo.NumCols, 0);
	for(UINT i = 0; i < m_terrainInfo.NumRows * m_terrainInfo.NumCols; ++i)
	{
		m_vecHeightmap[i] = (float)in[i] * m_terrainInfo.HeightScale
			+ m_terrainInfo.HeightOffset;
	}

}

void Terrain::Init( ID3D10Device* device,const TerrainInformation& info )
{
	m_pDevice = device;
	m_terrainInfo = info;

	m_uiNumVertex = m_terrainInfo.NumRows * m_terrainInfo.NumCols;
	m_uiNumFace = (m_terrainInfo.NumRows-1) * (m_terrainInfo.NumCols-1) * 2;

	LoadHeightMap();

	Smooth();

	BuildVB();
	BuildIB();
}

UINT Terrain::GetNumFaces()
{
	return m_uiNumFace;
}

ID3D10Buffer* Terrain::GetVertexBuffer()
{
	return m_pVB;
}

ID3D10Buffer* Terrain::GetIndexBuffer()
{
	return m_pIB;
}

TerrainInformation* Terrain::GetTerrainInfomation()
{
	return &m_terrainInfo;
}

void Terrain::SetBuffer()
{
	UINT stride = sizeof(Vertex);
	UINT offset = 0;
	m_pDevice->IASetVertexBuffers(0, 1, &m_pVB, &stride, &offset);

	m_pDevice->IASetIndexBuffer(m_pIB, DXGI_FORMAT_R32_UINT, 0);
}

void Terrain::Destory()
{
	ReleaseCOM(m_pVB);
	ReleaseCOM(m_pIB);
}
